// Package errcode提供在语义检查完成前的错误处理
//
// # 标准处理流程
//
// 错误处理上下文 [ErrCtx] 保存错误处理需要的信息
//
// 错误处理围绕这个错误处理上下文进行，处理没有关联的错误推荐使用不同错误处理上下文
//
// 报错通过 [ErrCtx.Panic] 进行
//
// 在一个单独的goroutin调用 [ErrCtx.Handle] 处理一个报错
//
//	代码如下：
//	var errctx = NewErrCtx() //创建错误处理上下文
//	ctx,cancel:=context.WithCancel(context.Background())
//	defer cancel() //在执行完毕时结束错误处理
//	go errctx.Handle(ctx,errfn func(ErrInfo){fmt.Println(err.String())},func(){}) //错误处理方式是将报错信息打印
//	//...词法分析，构建抽象语法树，语义检查，其中包含可能调用 [ErrCtx.Panic]的代码
//
// # 默认错误上下文
//
// 默认错误上下文 [DefaultErrCtx] 是由于 [ErrCtx] 出现晚于 [Panic] [Handle] [Errbol] 而诞生的产物
//
// 使用 默认错误上下文 的错误意味着报错和处理报错的状态是全局，不推荐在测试以外的情况使用
package errcode

import (
	"runtime"
	"strconv"
	"strings"
)

// 错误码，指定某一个错误
type ErrCode int

const (
	NoErr ErrCode = iota
	VARErr
	FUNCErr
	OneNoNumber
	EmptyLPAREN
	EmptyRPAREN
	OPbug
	NameRetain
	TypeIsNotEqual
	ASSIGNErr
	EmptyRbrace
	UnknownSymbol
	EmptyLBRACE
	NoName
	SymbolUseBeforeDeclaration
	IfErr
	NdSotf // Non-declaration statement outside the function
	ExprNoBool
	ElseErr
	AFIE //Assignment found in expression
	StringNoEnd
	ForErr
	MLCNoEnd             //multiline comment no end
	ForInitNoDeclVarStmt //Is not a declaration variable statement
	ForEndStmt
	SymbolRepeat
	UnexpectedComma
	CallErr
	InsuParame           //Insufficient parameters
	CallNoFunc           //Calling non functions
	FuncNoRetValue       //Function does not return a value
	BoolOpOnlyCmpOrLogic //Boolean type operations can only be comparative equality, inequality, or logical operations
	DeclFuncNoInFunc     //Cannot declare a function in a function at this time
	ReturnErr
	GotoErr
	NoLabel
	LabelErr
	BreakErr
	ContinueErr
	MainNoRetValue         //The main function does not return a value
	BreakStmtInForOrSwitch //The break statement should be in the for code block
	ContinueStmtInFor      //The continue statement should be in the for code block
	ConstErr
	TManyParame //too many parameters
	StructErr
	FieldErr
	NoIdent //Not an identifier
	UnknownType
	NoOSelectorL         //Not an optional selector lvalue
	SelectorLNoSelectorR //The selector left value does not have a selector right value
	UnexpectedRPAREN
	MallocReqParame     //Malloc requires a parameter
	ParameOfMallocAType //One parameter of malloc should be a type symbol
	StringNoSelfOpStmt  //String cannot use self operating statements
	SelfOpStmtErr
	FirstParameOfPrinfASting //The first parameter of printf should be a string
	PtrOpOnlyCmp             //Pointer type operations can only be comparative equal or unequal
	UnexpectedNil
	LeftOrRightValueSelectorEmpty       //The left or right value of the selector is empty
	SelectorLeftAndRightValueMustSymbol //The left and right value of the selector must be a symbol
	ProhibitCallInitFunc                //Prohibit calling init function
	ProhibitCallMainFunc                //Prohibit calling the main function
	UnexpectedLPAREN
	PackageDeclMustName         //The package declaration statement must include the package name
	PackageDeclOnlyName         //The package declaration should only contain the package name
	PackageDeclMustFirstLine    //The package declaration must appear on the first line
	DiffFileHavaDiffPackageDecl //Different file declarations have different package declarations
	SwitchErr
	CaseErr
	ExpectColonAtEnd //Expected a colon at the end
	CaseStmtInSwitch //The case statement should be in the switch
	DefaultErr
	ParameDeclHasNoType //Parameter declaration has no type
	DeclMethodInFunc    //Method cannot be declared in a function at this time
	MethodErr
	LbraceEndOfLine                     //Left brace should be at the end of the same line
	FirstParameTypeOfMethodACustomType  //The first parameter type of the method should be a custom type
	SecondParameOfUnsafeAddAnInt        //The second parameter of unsafeAdd should be an integer
	FirstParameOfUnsafeAddAPtr          //The first parameter of unsafeAdd should be a pointer
	FirstParameOfUnsafeConvertAPtr      //The first parameter of unsafeConvert should be a pointer
	SecondParameOfUnsafeConvertAPtrType //The second parameter of unsafeConvert should be a pointer type
	FirstParameOfUnsafeSizeAType        //The first parameter of unsafeSize should be a type
	AutoFreeErr
	ExprForAutoFreeShouldExprInt   //The expression for autofree should express integers
	CannotUseTypeAsRetValue        //Cannot use type as return value
	RecursiveTypeErr               //Recursive type error
	ImportShouldBeOutSideOfTheFunc //Import should be outside of the function
	ImportPathMustString           //The import path must be a string
	ImportErr
	CircularImportErr //Circular import error
	NotAnExportSymbol //Not an export symbol
	EnumErr
	EnumValueShouldHaveOneOnEachLine         //Enumeration values should have one on each line
	EnumValueShouldBeASymbol                 //The enumeration value should be a symbol
	PackageNameNoEqualDirName                //Package name does not equal directory name
	ParameForMallocSizeMustInt               //The parameter for mallocSize must be an integer
	EmptyEnum                                // empty enumeration
	CanOnlyConvertPointerTypeToUnsafePointer //Can only convert pointer type to unsafe__Pointer
	CanOnlyConvertFloatToInt                 //Can only convert float to int
	CanOnlyConvertIntToFloat                 //Can only convert int to float
	FieldDupName                             //Field duplicate name
	TheNumOfLeftRightBracketsNoMatch         //The number of left and right brackets does not match
	EnumValueDup                             //Enumeration value duplication
	IndexExprElemTypeErr                     //Index expression element type error
	TypeParameSyntaxErr                      //Type parameter Syntax error
	TypeParamrConstraintErr                  //Type parameter constraint error
	GenericInstantiationEmpty                //The generic instantiation list is empty
	UnequlaNumOfGen                          //Unequal number of generics
	OnlyTypePtrCanDeref                      //Only type pointers can be dereferenced
	UnrefNoVarAndConst                       //Unreferencing non variables and constants
	AdjacentOp                               //Adjacent Operators
	PackageNameCannotBeEqualUnsafe           //Package name cannot be equal to unsafe
	MainPackageMustHaveMainFunc              //The main package must have a main function
	NoMainPackageMustHaveNoMainFunc          //Non main packages must have no main function
	NameCannotStartWithGenerate              //The name cannot start with 'generate'
	EndOfNameCannot_mempool                  //The end of the name cannot be _mempool
	RetWithValueInFuncWithoutValue           //Returns with a return value in functions without a return value
	NoType
	NoConstInit
	NoBoolExpr
	NoExpr
	ArrayLenNotCompileTimeConstExpr     //Array length is not a compile-time constant expression
	UndeclRetValutTheRetStmthasRetValue //Undeclared return value The return statement has a return value
	UndeclRetValutHasRetStmt            //Undeclared return value has return statement
	DefaultStmtInSwitch                 //The default statement should be in the switch
	DestCannotSelfIncOrDec              //Destination Operands cannot be self-increasing or self-decreasing
	IndexExprErr
	IndexLenMustIsInt
	IndexExprXErr
	FreeArgMustPtr //The free argument must be a pointer
)

var (
	ErrCodeStrMap = [...]string{
		NoErr:                                    "无错误",
		VARErr:                                   "变量错误",
		FUNCErr:                                  "函数错误",
		OneNoNumber:                              "符号第一个不能是数字",
		EmptyLPAREN:                              "左小括号缺失",
		EmptyRPAREN:                              "右小括号缺失",
		OPbug:                                    "无法识别的操作",
		NameRetain:                               "名称是保留字",
		TypeIsNotEqual:                           "类型不相等",
		ASSIGNErr:                                "赋值错误",
		EmptyRbrace:                              "右大括号缺失",
		UnknownSymbol:                            "未知的符号",
		EmptyLBRACE:                              "左大括号缺失",
		NoName:                                   "没有名字",
		SymbolUseBeforeDeclaration:               "符号使用在声明之前",
		IfErr:                                    "if 错误",
		NdSotf:                                   "函数体外的非声明语句",
		ExprNoBool:                               "表达式不是表达一个bool值",
		ElseErr:                                  "else 错误",
		AFIE:                                     "在表达式中找到赋值",
		StringNoEnd:                              "字符串没有结束",
		ForErr:                                   "for 错误",
		MLCNoEnd:                                 "多行注释没有结束",
		ForInitNoDeclVarStmt:                     "for语句的初始化语句应该是声明变量语句",
		ForEndStmt:                               "for语句的结束语句应该是一个赋值语句或自操作语句",
		SymbolRepeat:                             "重复的符号",
		UnexpectedComma:                          "意外的逗号",
		CallErr:                                  "调用错误",
		InsuParame:                               "参数不足",
		CallNoFunc:                               "调用非函数",
		FuncNoRetValue:                           "函数没有返回值",
		BoolOpOnlyCmpOrLogic:                     "布尔类型进行的运算只能是比较相等或不相等或逻辑运算",
		DeclFuncNoInFunc:                         "现在不能在函数中声明函数",
		ReturnErr:                                "返回错误",
		GotoErr:                                  "goto错误",
		NoLabel:                                  "没有标签",
		LabelErr:                                 "标签错误",
		BreakErr:                                 "break错误",
		ContinueErr:                              "continue错误",
		MainNoRetValue:                           "main函数不用返回值",
		BreakStmtInForOrSwitch:                   "break语句应该在for代码块或switch代码块中",
		ContinueStmtInFor:                        "continue语句应该在for代码块中",
		ConstErr:                                 "常量错误",
		TManyParame:                              "参数过多",
		StructErr:                                "结构体错误",
		FieldErr:                                 "字段错误",
		NoIdent:                                  "不是标识符",
		UnknownType:                              "未知的类型",
		NoOSelectorL:                             "不是可选的选择器左值",
		SelectorLNoSelectorR:                     "选择器左值没有选择器右值",
		UnexpectedRPAREN:                         "意外的右小括号",
		MallocReqParame:                          "malloc需要一个参数",
		ParameOfMallocAType:                      "malloc的一个参数应该是类型符号",
		StringNoSelfOpStmt:                       "字符串不能使用自操作语句",
		SelfOpStmtErr:                            "自操作语句错误",
		FirstParameOfPrinfASting:                 "printf的第一个参数应该是字符串",
		PtrOpOnlyCmp:                             "指针类型进行的运算只能是比较相等或不相等",
		UnexpectedNil:                            "意外的nil",
		LeftOrRightValueSelectorEmpty:            "选择器左值或右值是空的",
		SelectorLeftAndRightValueMustSymbol:      "选择器左值和右值必须是符号",
		ProhibitCallInitFunc:                     "禁止调用init函数",
		ProhibitCallMainFunc:                     "禁止调用main函数",
		UnexpectedLPAREN:                         "意外的左小括号",
		PackageDeclMustName:                      "包声明必须包含包名",
		PackageDeclOnlyName:                      "包声明只应包含包名",
		PackageDeclMustFirstLine:                 "包声明必须出现在第一行",
		DiffFileHavaDiffPackageDecl:              "不同文件声明的包声明不同",
		SwitchErr:                                "switch 错误",
		CaseErr:                                  "case 错误",
		ExpectColonAtEnd:                         "冒号应该在末尾",
		CaseStmtInSwitch:                         "case语句应该在switch中",
		DefaultErr:                               "default 错误",
		ParameDeclHasNoType:                      "参数声明没有类型",
		DeclMethodInFunc:                         "现在不能在函数中声明方法",
		MethodErr:                                "方法错误",
		LbraceEndOfLine:                          "左大括号应该在同一行末尾",
		FirstParameTypeOfMethodACustomType:       "方法的第一个参数类型应该是自定义类型",
		SecondParameOfUnsafeAddAnInt:             "unsafe__Add的第二个参数应该是整数",
		FirstParameOfUnsafeAddAPtr:               "unsafe__Add的第一个参数应该是指针",
		FirstParameOfUnsafeConvertAPtr:           "unsafe__Convert的第一个参数应该是指针",
		SecondParameOfUnsafeConvertAPtrType:      "unsafe__Convert的第二个参数应该是指针类型",
		FirstParameOfUnsafeSizeAType:             "unsafe__Size第一个参数应该是类型",
		AutoFreeErr:                              "自动释放块错误",
		ExprForAutoFreeShouldExprInt:             "autofree的表达式应该表达整数",
		CannotUseTypeAsRetValue:                  "不能用类型作返回值",
		RecursiveTypeErr:                         "递归类型错误",
		ImportShouldBeOutSideOfTheFunc:           "import应该在函数外",
		ImportPathMustString:                     "导入路径必须是字符串",
		ImportErr:                                "import错误",
		CircularImportErr:                        "循环导入错误",
		NotAnExportSymbol:                        "不是导出符号",
		EnumErr:                                  "枚举错误",
		EnumValueShouldHaveOneOnEachLine:         "枚举值应该一行有一个",
		EnumValueShouldBeASymbol:                 "枚举值应该是符号",
		PackageNameNoEqualDirName:                "包名不等于目录名",
		ParameForMallocSizeMustInt:               "mallocSize的参数必须是整数",
		EmptyEnum:                                "空枚举",
		CanOnlyConvertPointerTypeToUnsafePointer: "只能转换指针类型为unsafe__Pointer",
		CanOnlyConvertFloatToInt:                 "只能转换float为int",
		CanOnlyConvertIntToFloat:                 "只能转换int为float",
		FieldDupName:                             "字段重名",
		TheNumOfLeftRightBracketsNoMatch:         "左右中括号数量不匹配",
		EnumValueDup:                             "枚举值重复",
		IndexExprElemTypeErr:                     "索引表达式元素类型必须是连续类型",
		TypeParameSyntaxErr:                      "类型参数语法错误",
		TypeParamrConstraintErr:                  "类型参数约束错误",
		GenericInstantiationEmpty:                "泛型实例化列表为空",
		UnequlaNumOfGen:                          "泛型数量不相等",
		OnlyTypePtrCanDeref:                      "只能对有类型指针解引用",
		UnrefNoVarAndConst:                       "对非变量和常量解引用",
		AdjacentOp:                               "相邻的运算符",
		PackageNameCannotBeEqualUnsafe:           "包名不能等于unsafe",
		MainPackageMustHaveMainFunc:              "main包必须有main函数",
		NoMainPackageMustHaveNoMainFunc:          "非main包必须没有main函数",
		NameCannotStartWithGenerate:              "名字开头不能是generate",
		EndOfNameCannot_mempool:                  "名字结尾不能是_mempool",
		RetWithValueInFuncWithoutValue:           "有返回值的return在无返回值的函数",
		NoType:                                   "没有类型",
		NoConstInit:                              "常量没有初始化",
		NoBoolExpr:                               "没有布尔表达式",
		NoExpr:                                   "没有表达式",
		ArrayLenNotCompileTimeConstExpr:          "数组长度不是编译期常量表达式",
		UndeclRetValutTheRetStmthasRetValue:      "未声明返回值返回语句却有返回值",
		UndeclRetValutHasRetStmt:                 "未声明返回值却有返回语句",
		DefaultStmtInSwitch:                      "dafeult语句应该在switch中",
		DestCannotSelfIncOrDec:                   "目的操作数不支持自增或自减",
		IndexExprErr:                             "索引表达式错误",
		IndexLenMustIsInt:                        "索引表达式长度必须是整数",
		IndexExprXErr:                            "索引表达式形如X[index],X必须是数组类型",
		FreeArgMustPtr:                           "free参数必须是指针",
	}
)

func (err ErrCode) String() string {
	return ErrCodeStrMap[err]
}

// 错误信息
type ErrInfo struct {
	//附加信息
	AddInfo Msg
	//文件名
	File string
	//错误提示
	Message string
	//堆栈信息
	stack []byte
	//行数
	Line int
}

// NewErrInfo 创建错误信息
//   - File是文件名
//   - Line是行号
//   - AddInfo是附加错误信息,可以为nil
//   - Message是错误的提示信息
func NewErrInfo(File string, Line int, AddInfo Msg, Message string) ErrInfo {
	ret := ErrInfo{
		File:    File,
		Line:    Line,
		Message: Message,
		AddInfo: AddInfo,
	}
	if Debug { //调试时操作
		//记录堆栈信息
		ret.stack = make([]byte, 10240)
		runtime.Stack(ret.stack, false)
	}
	return ret
}

// IsErr 判断方法参数给出的可以产生错误信息的内容与接收者是否相等
func (e ErrInfo) IsErr(File string, Line int, AddInfo Msg, code ...ErrCode) bool {
	if i, ok := e.AddInfo.(interface{ Is(Msg) bool }); ok { //如果有自定义判断是否相等的方法
		if !i.Is(AddInfo) {
			return false
		}
		return e.File == File && e.Line == Line && e.Message == ErrEnumStr(code...)
	}
	return e.File == File && e.Line == Line && e.AddInfo == AddInfo && e.Message == ErrEnumStr(code...)
}

func (e *ErrInfo) String() string {
	var buf strings.Builder
	buf.WriteString(e.File)
	buf.WriteString(":")
	if e.Line == -1 {
		buf.WriteString("未知行")
	} else {
		buf.WriteString(strconv.Itoa(e.Line))
	}
	buf.WriteString(": ")
	buf.WriteString(e.Message)
	if e.AddInfo != nil {
		buf.WriteString(e.AddInfo.Msg())
	}
	if Debug {
		buf.WriteString("\n")
		buf.Write(e.stack)
	}
	return buf.String()
}

var Debug = false
